// http://www.codeguru.com/forum/archive/index.php/t-205138.html
// http://msdn.microsoft.com/en-us/library/windows/desktop/aa374715%28v=vs.85%29.aspx
// http://davenport.sourceforge.net/ntlm.html  *** ver "NTLMSSP and SSPI" 

// Buena documentacion con el esquema...
// http://code.dblock.org/jna-acquirecredentialshandle-initializesecuritycontext-and-acceptsecuritycontext-establishing-an-authenticated-connection	
	// https://github.com/twall/jna

//http://msdn.microsoft.com/en-us/library/windows/desktop/aa379816%28v=vs.85%29.aspx



// http://drdobbs.com/184401688?pgno=3

// http://msdn.microsoft.com/en-us/library/aa374712%28VS.85%29.aspx					(General)
// http://msdn.microsoft.com/en-us/library/aa375506%28VS.85%29.aspx					(General)





#include <windows.h>
#define SECURITY_WIN32
#include <security.h>
//#include <stdio.h>

#include <iostream>
#include <boost/asio/buffer.hpp>



CredHandle clientCredential;
CredHandle serverCredential;// = new CredHandle();
CtxtHandle clientContext;// = new CtxtHandle();
CtxtHandle serverContext;// = new CtxtHandle();
//NativeLongByReference clientContextAttr = new NativeLongByReference();
ULONG clientContextAttr;
//NativeLongByReference serverContextAttr = new NativeLongByReference();
ULONG serverContextAttr;
TimeStamp serverExpiry;// = new TimeStamp();


bool is_null( CtxtHandle const& handle )
{
	return handle.dwLower == 0 && handle.dwUpper == 0;
}


void client_step1()
{
	
	TimeStamp clientExpiry;
	
	SECURITY_STATUS res = ::AcquireCredentialsHandle(
			0							//nullptr
			, "Negotiate"
			, SECPKG_CRED_OUTBOUND
			, 0							//nullptr
			, 0							//nullptr
			, 0							//nullptr
			, 0							//nullptr
			, &clientCredential
			, &clientExpiry
		);

	if (res != SEC_E_OK)
	{
		std::cout << "error";
	}

}



void server_step1()
{
	SECURITY_STATUS res = ::AcquireCredentialsHandle(
			0							//nullptr
			, "Negotiate"
			, SECPKG_CRED_INBOUND
			, 0							//nullptr
			, 0							//nullptr
			, 0							//nullptr
			, 0							//nullptr
			, &serverCredential
			, &serverExpiry);

	if (res != SEC_E_OK)
	{
		std::cout << "error";
	}
}


#define MAX_TOKEN_SIZE 12288

SecBuffer  Buffers_server[1];
char memory_server[MAX_TOKEN_SIZE];

SecBuffer  Buffers_client[1];
char memory_client[MAX_TOKEN_SIZE];


template <typename ConstBufferSequence>
void print_binary( ConstBufferSequence const& buffers )
{
	std::size_t size = boost::asio::buffer_size(buffers);
	const unsigned char* buffer = boost::asio::buffer_cast<const unsigned char*>(buffers);

	for ( int i = 0; i < size; ++i )
	{
		std::cout << (unsigned int)(unsigned char)buffer[i] << std::endl;
	}
}


void print_binary( char* buffer, std::size_t size )
{

	for ( int i = 0; i < size; ++i )
	{
		std::cout << (unsigned int)(unsigned char)buffer[i] << std::endl;
	}

}

void test_complete()
{
	//try 
	{
		// client ----------- acquire outbound credential handle
		client_step1();

		// client ----------- security context
		//client_step2();


		// server ----------- acquire inbound credential handle
		server_step1();


		// server ----------- security context


		SecBufferDesc serverToken;// = new SecBufferDesc(SECBUFFER_TOKEN, MAX_TOKEN_SIZE);
		serverToken.ulVersion = SECBUFFER_VERSION;
		serverToken.cBuffers = 1;
		serverToken.pBuffers = Buffers_server;

		Buffers_server[0].cbBuffer = MAX_TOKEN_SIZE;		//sizeof(pHeader);
		Buffers_server[0].BufferType = SECBUFFER_TOKEN;	//SECBUFFER_READONLY | SECBUFFER_DATA;
		Buffers_server[0].pvBuffer = memory_server;

		while(true) 
		{
			// client ----------- initialize security context, produce a client token
			// client token returned is always new
			SecBufferDesc clientToken;// = new SecBufferDesc(SECBUFFER_TOKEN, MAX_TOKEN_SIZE);
			clientToken.ulVersion = SECBUFFER_VERSION;
			clientToken.cBuffers = 1;
			clientToken.pBuffers = Buffers_client;

			Buffers_client[0].cbBuffer = MAX_TOKEN_SIZE;		//sizeof(pHeader);
			Buffers_client[0].BufferType = SECBUFFER_TOKEN;	//SECBUFFER_READONLY | SECBUFFER_DATA;
			Buffers_client[0].pvBuffer = memory_client;			
			


			//char m_szUserName[256];
			//DWORD dwUserNameSize = 256;
			//::GetUserNameEx(NameSamCompatible, m_szUserName, &dwUserNameSize);

			char m_szUserName[] = "HTTP/127.0.0.1\0";		//TODO: ver como construye esto.
			//TODO: ver que pasa en el caso de que la IP y el protocolo sea distinto...




			
			// server token is empty the first time
			int clientRc = ::InitializeSecurityContextA(
									&clientCredential,
									is_null(clientContext) ? 0 : &clientContext,							//clientContext.isNull() ? null : clientContext,
									m_szUserName,															//Advapi32Util.getUserName(),
									ISC_REQ_CONNECTION,
									0,
									SECURITY_NATIVE_DREP,
									&serverToken,
									0,
									&clientContext,
									&clientToken,
									&clientContextAttr,
									0							//nullptr
				);

			if (clientRc == SEC_E_OK)
			{
				std::cout << "error";
			}
			

			int xxx = SEC_I_CONTINUE_NEEDED;

			if (clientRc != SEC_I_CONTINUE_NEEDED) 
			{
				//throw RuntimeException("InitializeSecurityContext");
				std::cout << "error";
			}

			{
				//auto yyy = boost::asio::buffer(boost::asio::const_buffer(gptr(), (pptr() - gptr()) * sizeof(char_type)));
				auto yyy = boost::asio::buffer(memory_client, Buffers_client[0].cbBuffer);
				print_binary( yyy );
			}


			print_binary(memory_client, Buffers_client[0].cbBuffer);



			// server ----------- accept security context, produce a server token
			int serverRc = ::AcceptSecurityContext(
									&serverCredential,
									is_null(serverContext) ? 0: &serverContext,			//serverContext.isNull() ? null : serverContext,
									&clientToken,
									ISC_REQ_CONNECTION,
									SECURITY_NATIVE_DREP,
									&serverContext,
									&serverToken,
									&serverContextAttr,
									&serverExpiry
				);      

			if(serverRc != SEC_I_CONTINUE_NEEDED && serverRc != SEC_E_OK) 
			{
				std::cout << "error";
				//throw RuntimeException("AcceptSecurityContext");
			}
		}
	} 
	
	
	//finally	//TODO:
	{
		// release server context
		::DeleteSecurityContext(&serverContext);
		::FreeCredentialsHandle(&serverCredential);
		// release client context
		::DeleteSecurityContext(&clientContext);
		::FreeCredentialsHandle(&clientCredential);
	}



}



int main(int argc, char* argv[])
{
	//test_AcquireCredentialsHandleW();
	//test_domain();
	test_complete();

	return 0;
}






void test_AcquireCredentialsHandleW()
{
	// http://msdn.microsoft.com/en-us/library/windows/desktop/aa374715%28v=vs.85%29.aspx

	//package = "Negotiate"
	//	intent = Outbound


	//	outCredential = {System.Net.SafeFreeCredential_SECURITY}
	//_handle = {System.Net.SSPIHandle}
	//HandleHi = 81634376
	//	HandleLo = 81970256


	//	errorCode = UnsafeNclNativeMethods.SafeNetHandles_SECURITY.AcquireCredentialsHandleW(
	//	null, 
	//	package,
	//	(int)intent, 
	//	null, 
	//	IntPtr.Zero,
	//	null, 
	//	null,
	//	ref outCredential._handle,
	//	out timeStamp
	//	); 

	SEC_CHAR* package = "Negotiate";
	ULONG intent = SECPKG_CRED_OUTBOUND;

	//PCredHandle handle_;
	//PTimeStamp ptsExpiry;
	CredHandle handle_;
	TimeStamp tsExpires; 

	SECURITY_STATUS res = ::AcquireCredentialsHandle(
		0
		,"Negotiate"
		,intent
		,0
		,0
		,0
		,0
		,&handle_
		,&tsExpires
		);

	if (res != SEC_E_OK)
	{
		std::cout << "error";
	}

}

void test_other()
{
	char m_szComputerName[MAX_COMPUTERNAME_LENGTH + 1];
	DWORD dwComputerNameSize = MAX_COMPUTERNAME_LENGTH + 1;
	::GetComputerName (m_szComputerName, &dwComputerNameSize);

	char m_szUserName[256];
	DWORD dwUserNameSize = 256;
	::GetUserNameEx(NameSamCompatible, m_szUserName, &dwUserNameSize);

	printf("%s\n", m_szComputerName);
	printf("%s\n", m_szUserName);
}







//void test_domain()
//{
//	//char m_szComputerName[MAX_COMPUTERNAME_LENGTH + 1];
//	//DWORD dwComputerNameSize = MAX_COMPUTERNAME_LENGTH + 1;
//	//::GetComputerName (m_szComputerName, &dwComputerNameSize);
//
//	char m_szUserName[256];
//	DWORD dwUserNameSize = 256;
//	::GetUserNameEx(NameSamCompatible, m_szUserName, &dwUserNameSize);
//
//	//printf("%s\n", m_szComputerName);
//	printf("%s\n", m_szUserName);
//
//
//
//	//D:\Users\fernando.pelliccioni\Documents\RefSrc\Source\Windows 7 SP1 Netfx\3.5.1\DEVDIV\depot\DevDiv\releases\whidbey\Win7SP1\ndp\clr\src\BCL\System\Environment.cs\1\Environment.cs
//	//if (OSVersion.Platform == PlatformID.Win32NT) {
//	//	ret =  Win32Native.GetUserNameEx(Win32Native.NameSamCompatible, domainName, ref domainNameLen);
//	//	if (ret == 1) { 
//	//		string samName = domainName.ToString();
//	//		int index = samName.IndexOf('\\'); 
//	//		if( index != -1) { 
//	//			return samName.Substring(0, index);
//	//		} 
//	//	}
//	//	domainNameLen = domainName.Capacity;
//	//}
//}
